package com.onlyxiahui.framework.action.dispatcher;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;

import com.onlyxiahui.framework.action.dispatcher.annotation.ActionMapping;
import com.onlyxiahui.framework.action.dispatcher.common.ActionMethod;
import com.onlyxiahui.framework.action.dispatcher.common.ApplyInfo;
import com.onlyxiahui.framework.action.dispatcher.config.ActionConfigurer;
import com.onlyxiahui.framework.action.dispatcher.exception.ActionNotFoundException;
import com.onlyxiahui.framework.action.dispatcher.extend.ActionBox;
import com.onlyxiahui.framework.action.dispatcher.extend.ActionInterceptor;
import com.onlyxiahui.framework.action.dispatcher.extend.ActionMessage;
import com.onlyxiahui.framework.action.dispatcher.extend.ActionMethodInterceptor;
import com.onlyxiahui.framework.action.dispatcher.extend.ActionRequest;
import com.onlyxiahui.framework.action.dispatcher.extend.ActionResponse;
import com.onlyxiahui.framework.action.dispatcher.extend.ArgumentBox;
import com.onlyxiahui.framework.action.dispatcher.extend.ResultHandler;
import com.onlyxiahui.framework.action.dispatcher.extend.impl.DefaultActionBox;
import com.onlyxiahui.framework.action.dispatcher.extend.impl.DefaultActionMessage;
import com.onlyxiahui.framework.action.dispatcher.extend.impl.DefaultArgumentBox;
import com.onlyxiahui.framework.action.dispatcher.extend.impl.DefaultMethodArgumentResolver;
import com.onlyxiahui.framework.action.dispatcher.registry.ActionBoxRegistry;
import com.onlyxiahui.framework.action.dispatcher.registry.ActionInterceptorRegistry;
import com.onlyxiahui.framework.action.dispatcher.registry.ActionMethodInterceptorRegistry;
import com.onlyxiahui.framework.action.dispatcher.registry.ActionRegistry;
import com.onlyxiahui.framework.action.dispatcher.registry.MethodArgumentResolverRegistry;
import com.onlyxiahui.framework.action.dispatcher.registry.ResultHandlerRegistry;
import com.onlyxiahui.framework.action.dispatcher.util.ClassScaner;

/**
 * Date 2019-01-11 23:47:28<br>
 * Description
 * 
 * @author XiaHui<br>
 * @since 1.0.0
 */

public class ActionDispatcher {

	protected final Logger logger = LoggerFactory.getLogger(this.getClass());
	private final ActionRegistry actionRegistry = new ActionRegistry();
	private final ActionBoxRegistry actionBoxRegistry = new ActionBoxRegistry();
	private final ActionMethodInterceptorRegistry actionMethodInterceptorRegistry = new ActionMethodInterceptorRegistry();
	private final MethodArgumentResolverRegistry methodArgumentResolverRegistry = new MethodArgumentResolverRegistry();
	private final ActionInterceptorRegistry actionInterceptorRegistry = new ActionInterceptorRegistry();
	private final ResultHandlerRegistry resultHandlerRegistry = new ResultHandlerRegistry();
	private final ActionBox actionBox = new DefaultActionBox();
	private final DefaultMethodArgumentResolver defaultMethodArgumentResolver = new DefaultMethodArgumentResolver();

	@SuppressWarnings("unchecked")
	public void scan(String... path) {
		// 扫描包下面的所有被注解ActionMapping的类
		Set<Class<?>> classSet = ClassScaner.scan(path, ActionMapping.class);
		for (Class<?> classType : classSet) {
			actionRegistry.add(classType);
		}
	}

	@SuppressWarnings("unchecked")
	public void cover(String... path) {
		// 扫描com.hk包下面的所有被注解ActionMapping的类
		Set<Class<?>> classSet = ClassScaner.scan(path, ActionMapping.class);
		for (Class<?> classType : classSet) {
			actionRegistry.cover(classType);
		}
	}

	public void cover(Class<?> classType) {
		actionRegistry.cover(classType);
	}

	public void addConfig(ActionConfigurer actionConfigurer) {
		actionConfigurer.addActionBox(actionBoxRegistry);
		actionConfigurer.addAction(actionRegistry);
		actionConfigurer.addMethodArgumentResolver(methodArgumentResolverRegistry);
		actionConfigurer.addActionInterceptor(actionInterceptorRegistry);
		actionConfigurer.addActionMethodInterceptor(actionMethodInterceptorRegistry);
		actionConfigurer.addResultHandler(resultHandlerRegistry);
	}

	public Object action(String action, Object message, ActionResponse response) {
		ActionMessage am = resolver(action, message);
		return action(am, response, getArgumentBox());
	}

	public Object action(String action, Object message, ActionResponse response, ArgumentBox argumentBox) {
		Object returnValue = null;
		ActionMessage am = resolver(action, message);
		returnValue = action(am, response, argumentBox == null ? getArgumentBox() : argumentBox);
		return returnValue;
	}

	public Object action(ActionMessage am, ActionResponse response, ArgumentBox argumentBox) {
		Object returnValue = null;
		ActionRequest request = getActionRequest(am);
		returnValue = action(request, response, argumentBox);
		return returnValue;
	}

	public Object action(ActionRequest request, ActionResponse response, ArgumentBox argumentBox) {
		Object returnValue = null;
		ApplyInfo applyInfo = applyInterceptor(request, response, argumentBox);
		if (applyInfo.isApprove()) {
			returnValue = invokeForRequest(request, response, argumentBox);
		} else {
			returnValue = applyInfo.getValue();
		}
		returnValue = resultHandle(returnValue, request, response, argumentBox);
		return returnValue;
	}

	public Object resultHandle(Object value, ActionRequest request, ActionResponse response, ArgumentBox argumentBox) {
		List<ResultHandler> list = resultHandlerRegistry.getResultHandlerList();
		if (null != list) {
			for (ResultHandler rh : list) {
				value = rh.handle(value, request, response, argumentBox);
			}
		}
		return value;
	}

	public ActionRequest getActionRequest(ActionMessage am) {
		String actionCode = am.getAction();
		Object data = am.getMessage();
		ActionMethod actionMethod = actionRegistry.getActionMethod(actionCode);
		if (null == actionMethod) {
			StringBuilder sb = new StringBuilder();
			sb.append(actionCode);
			sb.append(" not found");
			throw new ActionNotFoundException(sb.toString());
		}
		Object action = null;
		if (null != actionMethod) {
			Class<?> type = actionMethod.getActionClass();
			action = getAction(type);
		}
		ActionRequest actionRequest = new ActionRequestImpl(action, actionMethod, data);
		return actionRequest;
	}

	public Object getAction(Class<?> type) {
		Object a = actionBoxRegistry.getAction(type);
		if (a == null) {
			a = actionBox.getAction(type);
		}
		return a;
	}

	public ApplyInfo applyInterceptor(ActionRequest request, ActionResponse response, ArgumentBox argumentBox) {
		ApplyInfo applyInfo = null;
		List<ActionInterceptor> list = actionInterceptorRegistry.getActionInterceptorList();
		if (null != list) {
			for (ActionInterceptor ai : list) {
				applyInfo = ai.previous(request, response, argumentBox);
				if (null == applyInfo) {
					applyInfo = new ApplyInfo();
					break;
				} else if (!applyInfo.isApprove()) {
					break;
				}
			}
		}
		if (null == applyInfo) {
			applyInfo = new ApplyInfo();
			applyInfo.setApprove(true);
		}
		return applyInfo;
	}

	public Object invokeForRequest(ActionRequest request, ActionResponse response, ArgumentBox argumentBox) {
		Object[] args = getMethodArgumentValues(request, response, argumentBox);
		if (logger.isTraceEnabled()) {
//			StringBuilder sb = new StringBuilder("Invoking [");
//			logger.debug(sb.toString());
		}
		Object bean = request.getAction();
		ActionMethod actionMethod = request.getActionMethod();
		Object returnValue = null;
		ApplyInfo applyInfo = applyActionMethodInterceptor(bean, actionMethod.getMethod(), args, actionMethod.getMethodParameters(), request, response, argumentBox);
		if (applyInfo.isApprove()) {
			returnValue = doInvoke(bean, actionMethod.getMethod(), args);
			if (logger.isTraceEnabled()) {
				logger.trace("Method [" + actionMethod.getMethod().getName() + "] returned [" + returnValue + "]");
			}
		} else {
			returnValue = applyInfo.getValue();
		}
		return returnValue;
	}

	public ApplyInfo applyActionMethodInterceptor(Object bean, Method method, Object[] args, MethodParameter[] parameter, ActionRequest request, ActionResponse response, ArgumentBox argumentBox) {
		ApplyInfo applyInfo = null;
		List<ActionMethodInterceptor> list = actionMethodInterceptorRegistry.getList();
		if (null != list) {
			for (ActionMethodInterceptor ai : list) {
				applyInfo = ai.intercept(bean, method, args, parameter, request, response, argumentBox);
				if (null == applyInfo) {
					applyInfo = new ApplyInfo();
					break;
				} else if (!applyInfo.isApprove()) {
					break;
				}
			}
		}
		if (null == applyInfo) {
			applyInfo = new ApplyInfo();
			applyInfo.setApprove(true);
		}
		return applyInfo;
	}

	public Object[] getMethodArgumentValues(ActionRequest request, ActionResponse response, ArgumentBox argumentBox) {
		ActionMethod actionMethod = request.getActionMethod();
		MethodParameter[] parameters = actionMethod.getMethodParameters();
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			// parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			// GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
			if (this.methodArgumentResolverRegistry.supportsParameter(parameter)) {
				args[i] = this.methodArgumentResolverRegistry.resolveArgument(parameter, request, response, argumentBox);
				continue;
			}
			if (args[i] == null) {
				args[i] = defaultMethodArgumentResolver.resolveArgument(parameter, request, response, argumentBox);
			}
			if (args[i] == null) {
				String msg = ("No suitable resolver for argument" + i);
				throw new IllegalStateException(msg);
			}
		}
		return args;
	}

	public Object doInvoke(Object bean, Method method, Object... args) {
		makeAccessible(method);
		try {
			return method.invoke(bean, args);
		} catch (Exception ex) {
			String message = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
			throw new IllegalStateException(message, ex);
		}
	}

	public void makeAccessible(Method method) {
		boolean isNotAccessible = (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible();
		if (isNotAccessible) {
			method.setAccessible(true);
		}
	}

	public ArgumentBox getArgumentBox() {
		ArgumentBox ab = new DefaultArgumentBox();
		return ab;
	}

	public ActionMessage resolver(String action, Object message) {
		ActionMessage am = new DefaultActionMessage(action, message);
		return am;
	}

	public ActionRegistry getActionRegistry() {
		return actionRegistry;
	}

	public ActionBoxRegistry getActionBoxRegistry() {
		return actionBoxRegistry;
	}

	public MethodArgumentResolverRegistry getMethodArgumentResolverRegistry() {
		return methodArgumentResolverRegistry;
	}

	public ActionInterceptorRegistry getActionInterceptorRegistry() {
		return actionInterceptorRegistry;
	}

	public ResultHandlerRegistry getResultHandlerRegistry() {
		return resultHandlerRegistry;
	}

	public ActionBox getActionBox() {
		return actionBox;
	}

	class ActionRequestImpl implements ActionRequest {

		private final Map<Object, Object> attributeMap = new HashMap<>();
		private ActionMethod actionMethod;
		private Object action;
		private Object data;

		ActionRequestImpl(Object action, ActionMethod actionMethod, Object data) {
			this.action = action;
			this.actionMethod = actionMethod;
			this.data = data;
		}

		@Override
		public Object getAction() {
			return this.action;
		}

		@Override
		public Object getData() {
			return this.data;
		}

		@Override
		public ActionMethod getActionMethod() {
			return this.actionMethod;
		}

		@Override
		public void addAttribute(Object key, Object value) {
			attributeMap.put(key, value);
		}

		@SuppressWarnings("unchecked")
		@Override
		public <T> T getAttribute(Object key) {
			return (T) attributeMap.get(key);
		}
	}
}
